#include "Animator.h"

//=============================================================================
Animation::Animation (void* animTarget, const String& animName, const String& animationTypeName)
:	target (animTarget),
	name (animName),
	typeName (animationTypeName)
{
	jassert (target != 0);
}

Animation::~Animation ()
{
}

void* Animation::getTarget ()
{
	return target;
}

String Animation::getTypeName () const
{
	return typeName;
}

String Animation::getName () const
{
	return name;
}

//=====================================
AnimatorClock::AnimatorClock ()
{
	timerInterval = 10;
	running = false;
}

AnimatorClock::~AnimatorClock ()
{
	stop ();
	clearSingletonInstance ();
}

void AnimatorClock::setTimerInterval (int ms)
{
	timerInterval = ms;
}

int AnimatorClock::getTimerInterval ()
{
	return timerInterval;
}

void AnimatorClock::connectAnimator (Animator* animator)
{
	if (!animators.contains (animator)) animators.add (animator);
	if ((animators.size () > 0) && !running) start ();
}

void AnimatorClock::disconnectAnimator (Animator* animator)
{
	animators.removeValue (animator);
	if (animators.size () == 0) stop ();
}

void AnimatorClock::disconnectAllAnimators ()
{
	stop ();
	animators.clear ();
}

void AnimatorClock::start ()
{
	stopTimer ();

	prevTime = Time::getCurrentTime ();
	running = true;
	startTimer (timerInterval);
}

void AnimatorClock::stop ()
{
	running = false;
	stopTimer ();
}

void AnimatorClock::timerCallback ()
{
	Time currentTime = Time::getCurrentTime ();
	int64 elapsed = (currentTime - prevTime).inMilliseconds ();

	for (int i=0; i<animators.size(); i++)
	{
		Animator* a = animators.getUnchecked (i);

		if (!a->useTimeSlice ((int) elapsed))
		{
			// The animator's tasks have all finished, so we
			// can disconnect it...
			disconnectAnimator (a);
			--i;
		}
	}
	prevTime = currentTime;
}

//=============================================================================
// Internal class for managing animations for a single target
class TargetAnimator
{
public:

	TargetAnimator (void* animTarget)
	:	target (animTarget)
	{
		jassert (target != 0);
	}

	~TargetAnimator ()
	{
	}

	void* getTarget ()
	{
		return target;
	}

	bool useTimeSlice (const int elapsed)
	{
		bool anyActive = false;
		tasks.lockArray ();

		for (int i=0; i<tasks.size(); i++)
		{
			if (!tasks.getUnchecked (i)->useTimeSlice (elapsed))
			{
				// The task has ended (returned false...)
				tasks.remove (i);
				--i;
			}
			else
			{
				anyActive = true;
			}
		}
		tasks.unlockArray ();
		return anyActive;
	}

	void addTask (Animation* task)
	{
		jassert (task && task->getTarget() == target);
		tasks.lockArray ();
		for (int i=0; i<tasks.size(); i++)
		{
			Animation* t = tasks.getUnchecked (i);
			if (t->getTypeName() == task->getTypeName())
			{
				if (t->getName() == task->getName())
				{
					tasks.remove (i);
					break;
				}
			}
		}
		tasks.add (task);
		tasks.unlockArray ();
	}

	void removeTask (const String& type, const String& name, bool skipToEnd)
	{
		tasks.lockArray ();
		for (int i=0; i<tasks.size(); i++)
		{
			Animation* t = tasks.getUnchecked (i);
			if (t->getTypeName() == type)
			{
				if (t->getName() == name)
				{
					if (skipToEnd) t->skipToEnd();
					tasks.remove (i);
					break;
				}
			}
		}
		tasks.unlockArray ();
	}

	void removeAll (bool skipToEnd)
	{
		tasks.lockArray ();
		while (tasks.size() > 0)
		{
			if (skipToEnd) tasks.getLast ()->skipToEnd ();
			tasks.removeLast ();
		}
		tasks.unlockArray ();
	}

	friend class Animator;

private:

	OwnedArray<Animation,CriticalSection> tasks;
	void* target;

};

//=============================================================================
Animator::Animator ()
{
}

Animator::~Animator ()
{
	stopAll (false);
}

void Animator::pauseAll ()
{
	disconnectFromClock ();
}

void Animator::resumeAll ()
{
	connectToClock ();
}

void Animator::stopAll (bool skipToEndOfAnimation)
{
	while (targetAnimators.size() > 0)
	{
		TargetAnimator* targetAnimator = (TargetAnimator*) targetAnimators.getLast ();
		jassert (targetAnimator != 0);

		targetAnimator->removeAll (skipToEndOfAnimation);

		frameFinished (targetAnimator->getTarget ());
		targetAnimators.removeLast ();
		delete targetAnimator;
	}
}

void Animator::connectToClock ()
{
	AnimatorClock::getInstance ()->connectAnimator (this);
}

void Animator::disconnectFromClock ()
{
	AnimatorClock::getInstance ()->disconnectAnimator (this);
}


void Animator::start (Animation* anim)
{
	jassert (anim);

	void* target = anim->getTarget ();
	TargetAnimator* targetAnimator = 0;

	// Ensure we have a target set...
	for (int i=0; i<targetAnimators.size(); i++)
	{
		TargetAnimator* ta = (TargetAnimator*) targetAnimators.getUnchecked(i);
		if (ta->getTarget() == target)
		{
			targetAnimator = ta;
			break;
		}
	}
	if (targetAnimator == 0)
	{
		targetAnimator = new TargetAnimator (target);
		targetAnimators.add (targetAnimator);
	}

	targetAnimator->addTask (anim);
	// Ensure the clock is running for it...
	connectToClock ();
}

void Animator::stop (void* target, const String& typeName, const String& name, bool skipToEnd)
{
	// Ensure we have a target set...
	for (int i=0; i<targetAnimators.size(); i++)
	{
		TargetAnimator* targetAnimator = (TargetAnimator*) targetAnimators.getUnchecked(i);
		if (targetAnimator->getTarget() == target)
		{
			targetAnimator->removeTask (typeName, name, skipToEnd);

			if (targetAnimator->tasks.size () == 0)
			{
				frameFinished (targetAnimator->getTarget ());
				targetAnimators.remove (i);
				delete targetAnimator;
			}

			if (targetAnimators.size() == 0) disconnectFromClock ();
			return;			
		}
	}
}

bool Animator::useTimeSlice (int elapsedMs)
{
	bool anyActive = false;

	for (int i=0; i<targetAnimators.size(); i++)
	{
		TargetAnimator* targetAnimator = (TargetAnimator*) targetAnimators.getUnchecked(i);
		// grab the target now in case its animator gets deleted for finishing
		void* target = targetAnimator->getTarget();
		if (!targetAnimator->useTimeSlice (elapsedMs))
		{
			// The target's taskset has ended (returned false...)
			targetAnimators.remove (i);
			delete targetAnimator;
			--i;
		}
		else
		{
			anyActive = true;
		}
		frameFinished (target);
	}
	return anyActive;
}

void Animator::frameFinished (void* target)
{
}


juce_ImplementSingleton (AnimatorClock);